#include <kernel/kobject.h>
#include <kernel/initcall.h>
#include <kernel/syslog.h>
#include <mm/mem.h>
#include <drivers/platform.h>
#include <drivers/spi.h>
#include <kernel/fdt.h>


static void gpio_spi_8bit_msb_transfer(struct spi_master *master, uint8_t mode, struct spi_transfer *xfer)
{
	uint8_t i;
    uint8_t *tx_data = (uint8_t *)xfer->tx_buf;
    uint8_t *rx_data = (uint8_t *)xfer->rx_buf;
    uint64_t len = xfer->len;

    while(len -- ) {
        uint8_t recv = 0, send = 0;

        if(tx_data != NULL) {
            send = *(tx_data++);
        }

        if (mode == SPI_MODE_0 || mode == SPI_MODE_1) {
            gpio_set_value(master->pin.clk, IO_LOW);
        } else {
            gpio_set_value(master->pin.clk, IO_HIGH);
        }

        for (int i = 0; i < 8; i++) {
            gpio_set_value(master->pin.mosi, send & (0x80 >> i));

            if (mode == SPI_MODE_0 || mode == SPI_MODE_2) {
                gpio_set_value(master->pin.clk, IO_HIGH);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x80 >> i);
                }

                gpio_set_value(master->pin.clk, IO_LOW);
            } else {
                gpio_set_value(master->pin.clk, IO_LOW);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x80 >> i);
                }

                gpio_set_value(master->pin.clk, IO_HIGH);
            }
        }

        if(rx_data != NULL) {
            *(rx_data++) = recv;
        }
    }
}


static void gpio_spi_8bit_lsb_transfer(struct spi_master *master, uint8_t mode, struct spi_transfer *xfer)
{
	uint8_t i;
    uint8_t *tx_data = (uint8_t *)xfer->tx_buf;
    uint8_t *rx_data = (uint8_t *)xfer->rx_buf;
    uint64_t len = xfer->len;

    while(len -- ) {
        uint8_t recv = 0, send = 0;

        if(tx_data != NULL) {
            send = *(tx_data++);
        }

        if (mode == SPI_MODE_0 || mode == SPI_MODE_1) {
            gpio_set_value(master->pin.clk, IO_LOW);
        } else {
            gpio_set_value(master->pin.clk, IO_HIGH);
        }

        for (int i = 0; i < 8; i++) {
            gpio_set_value(master->pin.mosi, send & (0x1 << i));

            if (mode == SPI_MODE_0 || mode == SPI_MODE_2) {
                gpio_set_value(master->pin.clk, IO_HIGH);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x1 << i);
                }

                gpio_set_value(master->pin.clk, IO_LOW);
            } else {
                gpio_set_value(master->pin.clk, IO_LOW);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x1 << i);
                }

                gpio_set_value(master->pin.clk, IO_HIGH);
            }
        }

        if(rx_data != NULL) {
            *(rx_data++) = recv;
        }
    }
}


static void gpio_spi_16bit_msb_transfer(struct spi_master *master, uint8_t mode, struct spi_transfer *xfer)
{
	uint8_t i;
    uint16_t *tx_data = (uint16_t *)xfer->tx_buf;
    uint16_t *rx_data = (uint16_t *)xfer->rx_buf;
    uint64_t len = xfer->len;

    while(len) {
        uint16_t recv = 0, send = 0;

        if(tx_data != NULL) {
            send = *(tx_data++);
        }

        if (mode == SPI_MODE_0 || mode == SPI_MODE_1) {
            gpio_set_value(master->pin.clk, IO_LOW);
        } else {
            gpio_set_value(master->pin.clk, IO_HIGH);
        }

        for (int i = 0; i < 16; i++) {
            gpio_set_value(master->pin.mosi, send & (0x8000 >> i));

            if (mode == SPI_MODE_0 || mode == SPI_MODE_2) {
                gpio_set_value(master->pin.clk, IO_HIGH);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x8000 >> i);
                }

                gpio_set_value(master->pin.clk, IO_LOW);
            } else {
                gpio_set_value(master->pin.clk, IO_LOW);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x8000 >> i);
                }

                gpio_set_value(master->pin.clk, IO_HIGH);
            }
        }

        if(rx_data != NULL) {
            *(rx_data++) = recv;
        }

        len -= 2;
    }
}


static void gpio_spi_16bit_lsb_transfer(struct spi_master *master, uint8_t mode, struct spi_transfer *xfer)
{
	uint8_t i;
    uint16_t *tx_data = (uint16_t *)xfer->tx_buf;
    uint16_t *rx_data = (uint16_t *)xfer->rx_buf;
    uint64_t len = xfer->len;

    while(len) {
        uint16_t recv = 0, send = 0;

        if(tx_data != NULL) {
            send = *(tx_data++);
        }

        for (int i = 0; i < 16; i++) {
            gpio_set_value(master->pin.mosi, send & (0x1 << i));

            if (mode == SPI_MODE_0 || mode == SPI_MODE_2) {
                gpio_set_value(master->pin.clk, IO_HIGH);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x1 << i);
                }

                gpio_set_value(master->pin.clk, IO_LOW);
            } else {
                gpio_set_value(master->pin.clk, IO_LOW);

                // receive data
                if(gpio_get_value(master->pin.miso) == IO_HIGH) {
                    recv |= (0x1 << i);
                }

                gpio_set_value(master->pin.clk, IO_HIGH);
            }
        }

        len -= 2;
    }
}


static int gpio_spi_transfer(struct spi_device *spi, struct spi_transfer *xfer)
{
    struct spi_master *master = spi->master;

    spi_gpio_set_cs(spi, true);

    if(spi->lsb) {
        if(spi->bits == 8) {
            gpio_spi_8bit_lsb_transfer(master, spi->mode, xfer);
        }
        else if(spi->bits == 16) {
            gpio_spi_16bit_lsb_transfer(master, spi->mode, xfer);
        }
    }
    else {
        if(spi->bits == 8) {
            gpio_spi_8bit_msb_transfer(master, spi->mode, xfer);
        }
        else if(spi->bits == 16) {
            gpio_spi_16bit_msb_transfer(master, spi->mode, xfer);
        }
    }

    spi_gpio_set_cs(spi, false);

    return 0;
}


static void gpio_spi_setup(struct spi_device *spi)
{

}


static void gpio_spi_set_cs(struct spi_device *spi, bool enable)
{
    spi_gpio_set_cs(spi, enable);
}


static int gpio_spi_driver_probe(struct platform_device *dev)
{
    fdt_node_t *spi_node = dev->device.dt_node;

    size_t clk_pin, miso_pin, mosi_pin;
    struct spi_gpio pin;

    if(spi_get_cs_pins(spi_node, pin.cs) < 0) {
        return -1;
    }

    if(fdt_read_prop_int(spi_node, "clk-gpio", &clk_pin)) {
        SPI_SYSLOG_ERROR("%s: not found clk pin", spi_node->name);
        return -1;
    }
    if(gpio_request(clk_pin)) {
        SPI_SYSLOG_ERROR("%s: clk gpio request failed", spi_node->name);
        return -1;
    }
    SPI_DEBUG_TRACE("%s: clk gpio(%d) request success", spi_node->name, clk_pin);
    pin.clk = clk_pin;
    gpio_direction_output(clk_pin, IO_HIGH);

    if(fdt_read_prop_int(spi_node, "miso-gpio", &miso_pin)) {
        SPI_SYSLOG_INFO("%s: not found miso pin", spi_node->name);
        return -1;
    }
    if(gpio_request(miso_pin)) {
        SPI_SYSLOG_ERROR("%s: miso gpio request failed", spi_node->name);
        return -1;
    }
    SPI_DEBUG_TRACE("%s: miso gpio(%d) request success", spi_node->name, miso_pin);
    pin.miso = miso_pin;
    gpio_direction_input(miso_pin);

    if(fdt_read_prop_int(spi_node, "mosi-gpio", &mosi_pin)) {
        SPI_SYSLOG_INFO("%s: not found mosi pin", spi_node->name);
        return -1;
    }
    if(gpio_request(mosi_pin)) {
        SPI_SYSLOG_ERROR("%s: mosi gpio request failed", spi_node->name);
        return -1;
    }
    SPI_DEBUG_TRACE("%s: mosi gpio(%d) request success", spi_node->name, mosi_pin);
    pin.mosi = mosi_pin;
    gpio_direction_output(mosi_pin, IO_HIGH);

    struct spi_master *master = kmalloc(sizeof(struct spi_master));
    if(master == NULL) {
        SPI_SYSLOG_ERROR("alloc failed for spi master");
    }

    master->io_base = dev->io_base;
    master->transfer = gpio_spi_transfer;
    master->setup = gpio_spi_setup;
    master->set_cs = gpio_spi_set_cs;
    master->pin = pin;

    platform_set_platform_data(dev, master);

    fdt_node_t *child =  NULL;
    fdt_for_each_node_child(spi_node, child) {

        struct spi_device *spi_dev = kmalloc(sizeof(struct spi_device));
        if(spi_dev == NULL) {
            SPI_SYSLOG_ERROR("alloc spi device failed");
            return -1;
        }

        if(spi_device_get_config_from_fdt(child, spi_dev)) {
            kfree(spi_dev);
            return -1;
        }

        spi_dev->device.name = child->name;
        spi_dev->master = master;

        if(spi_device_register(spi_dev)) {
            SPI_SYSLOG_ERROR("spi device register failed");
            return -1;
        }
    }
    return 0;
}


static void gpio_spi_driver_remove(struct platform_device *dev)
{

}


static struct of_device_id gpio_spi_driver_match[] = {
    {.compatible = "gpio-spi", },
    { }
};


static struct platform_driver gpio_spi_pdrv = {
    .driver = {
        .name = "gpio-spi",
        .of_match_table = gpio_spi_driver_match,
    },
    .probe = gpio_spi_driver_probe,
    .remove = gpio_spi_driver_remove,
};

static int gpio_spi_driver_register(void)
{
    return platform_driver_register(&gpio_spi_pdrv);
}


driver_initcall(gpio_spi_driver_register);
